use anchor_lang::prelude::*;
use anchor_lang::prelude::borsh::{BorshDeserialize, BorshSerialize};
use solana_program::secp256k1_recover::secp256k1_recover;
use sha3::{Digest, Keccak256};

use anchor_lang::solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    instruction::{AccountMeta, Instruction},
    program::invoke,
    pubkey::Pubkey,
};

declare_id!("6Lp1VSxcaRLNPYaptWYebb4v64JbQJf4UMrSCiGbtapD");



const SIGHASH_GLOBAL_NAMESPACE : &str= "global";

/// The function discrminator is constructed from these 8 bytes. Typically, the namespace is
/// "global" or "state"
pub fn ix_discriminator(name: &str) -> [u8; 8] {
    let preimage = format!("{}:{}", SIGHASH_GLOBAL_NAMESPACE, name);
    let mut sighash = [0u8; 8];
    sighash.copy_from_slice(
        &anchor_lang::solana_program::hash::hash(preimage.as_bytes()).to_bytes()[..8],
    );
    sighash
}

#[repr(C)]
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct WmbReceiveParam {
    data: Vec<u8>
}
pub fn _decode_and_verify( encoded_info:  Vec<u8>, signatures: Vec<Secep256k1Signature>)-> Result<()> {


    let mut hasher = Keccak256::new();
    hasher.update(encoded_info);
    let data_hash_v:[u8;32] = hasher.finalize().into();

    for signature in signatures {
        let pubkey = secp256k1_recover(&data_hash_v, signature.recid,&signature.signature).unwrap();
        msg!("pubkey: {:?}", pubkey.to_bytes());

    }
    Ok(())
}

#[program]
pub mod message_xport {
  
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("in initialize...haolifeng");
        Ok(())
    }

    pub fn decode_and_verify(ctx:Context<DecodeAndVerify> , encoded_info:  Vec<u8>, signatures: Vec<Secep256k1Signature>)-> Result<()> {


        let mut hasher = Keccak256::new();
        hasher.update(encoded_info);
        let data_hash_v:[u8;32] = hasher.finalize().into();

        for signature in signatures {
            let pubkey = secp256k1_recover(&data_hash_v, signature.recid,&signature.signature).unwrap();
            msg!("pubkey: {:?}", pubkey.to_bytes());

        }
        Ok(())
    }

    // network_id   : to chain id;
    // contract_address : to contract on to_chain
    // functionCallData :
    //source_contract_address: the contract call this contract
    pub fn outbound_call(ctx: Context<OutboundCall>, network_id:u128, contract_address: Vec<u8>, functionCallData: Vec<u8>, source_contract_address: [u8;32],authority_bump:u8,call_program_id:Pubkey) -> Result<()> {

        msg!("outbound_call, networkId:{:?}, contract_address: {:?}, functionCallData: {:?}, source_contract_address: {:?}", network_id.clone(), contract_address.clone(), functionCallData.clone(), source_contract_address.clone());
        let chain_id = 12345678;
        let finally_function_call_data = OutBoundFunctionCallData {
            functionCallData,
            networkId:chain_id.clone(),
            contractAddress:source_contract_address.clone()
        };

        let task_id_data = TaskIdData {
            source_network_id:chain_id.clone(),
            source_contract_address:source_contract_address.clone(),
            target_network_id:network_id.clone(),
            target_contract_address:contract_address.clone(),
            finally_function_call_data:finally_function_call_data.clone(),
            nonce:0,
        };

        let data = task_id_data.try_to_vec().unwrap();
        let mut hasher = Keccak256::new();
        hasher.update(data);
        let task_id:[u8;32] = hasher.finalize().into();

        msg!("CrosschainFunctionCall, taskId:{:?},networkId{:?}, contract_address:{:?}, finally_function_call_data{:?}", task_id.clone(), network_id.clone(), contract_address.clone(), finally_function_call_data.clone().try_to_vec().unwrap());
        Ok(())
    }
    pub fn inbound_call(ctx: Context<DecodeAndVerify>, network_id:u128, encode_info: Vec<u8>, encoded_proof:Vec<u8>) -> Result<()> {

        msg!("inbound_call, network_id: {:?}", network_id);

        let encode_proof_data = EncodeProofData::try_from_slice(&encoded_proof).unwrap();
        let n = encode_proof_data.signatures.len();
        msg!("encode_proof_data.signatures.len: {:?}", &n);

        let mut signatures:Vec<Secep256k1Signature> = Vec::new();

            for i in 0..n {
                let signatuers_data = &encode_proof_data.signatures[i];
                let signature = signatuers_data.signature.clone();
                let recid = signatuers_data.recid;

                let proof: [u8; 64] = signature.clone();
                signatures.push(Secep256k1Signature {
                    signature:proof,
                    recid:recid,
                })
                
            }

        _decode_and_verify(encode_info.clone(), signatures)?;

        let encode_info = EncodeInfo::try_from_slice(&encode_info).unwrap();


        let task_id = &encode_info.taskId;
        let receipt_network_id = &encode_info.networkId;
        let call_contract_addr = &encode_info.contractAddress;
        let encode_funcation_call_data = &encode_info.functionCallData;

        let inbound_function_call_data = InBoundFunctionCallData::try_from_slice(&encode_funcation_call_data).unwrap();

        let source_contract = &inbound_function_call_data.contractAddress;
        let source_chain_id = &inbound_function_call_data.networkId;
        msg!("encode_info 444 {:?},{:?}", source_contract, source_chain_id);

        let functaionCallData  = FunctionCallData::try_from_slice(&inbound_function_call_data.functionCallData).unwrap();

        let receipt_msg = &functaionCallData.messageFunc;
        let message_data = &functaionCallData.messageData;
        
        msg!("encode_info 5555 {:?},{:?}", receipt_msg, message_data);

        let mut ix_data = Vec::new();
        let r1 = ix_discriminator("wmb_receive");//wmb_receive
        let wmb_receive_instruction = WmbReceiveParam {
            data:message_data.clone(),
        };
        let r2 = wmb_receive_instruction.try_to_vec().unwrap();

        ix_data.extend_from_slice(&r1);
        ix_data.extend_from_slice(&r2);

        let accounts_meta = vec![];
        let ix= &Instruction::new_with_bytes(
            //Pubkey::try_from_slice(call_contract_addr)?,
            ctx.accounts.app_program.key(),
            &ix_data,
            accounts_meta,
        );
        
        let account_infos = vec![
            
        ];
        invoke(ix, &account_infos);


        Ok(())
    }

}

#[derive(Accounts)]
pub struct Initialize {}

#[derive(Accounts)]
pub struct DecodeAndVerify<'info> {
    /// CHECK: This account is neither written to nor read from.
    pub app_program: UncheckedAccount<'info>
}

#[derive(Accounts)]
#[instruction(network_id:u128, contract_address: Vec<u8>, functionCallData: Vec<u8>, source_contract_address: [u8;32],authority_bump:u8,call_program_id:Pubkey)]
pub struct OutboundCall<'info> {
    #[account(seeds=[b"outbound_call"],
    bump = authority_bump,
    seeds::program  = call_program_id,
    )]
    pub authority_pda:Signer<'info>
}


// Instruction parameters



#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct Secep256k1Signature {
    pub signature: [u8;64],
    pub recid: u8,
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct FunctionCallData {
    messageFunc:String,  // wmbReceive
    messageData:Vec<u8>,
}
#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct InBoundFunctionCallData {
    functionCallData: Vec<u8>,
    networkId: u128,  // other chain id
    contractAddress: Vec<u8>, // other chain contract address, such as evm
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug,  Clone)]
pub struct OutBoundFunctionCallData {
    functionCallData: Vec<u8>,
    networkId: u128,
    contractAddress: [u8;32], // solana contract address , publickey
}
#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct TaskIdData {
    source_network_id:u128,
    source_contract_address:[u8;32], //solanal contract address, publick
    target_network_id:u128,
    target_contract_address: Vec<u8>,  // other chain contract address
    finally_function_call_data: OutBoundFunctionCallData,
    nonce:u128,
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct EncodeInfo {
    pub taskId: Vec<u8>,
    pub networkId: u128,
    pub contractAddress: [u8;32], // solana contract address
    pub functionCallData: Vec<u8>
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct SignatureVale {
    recid: u8,
    signature: [u8;64],
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
pub struct EncodeProofData {
    signatures:Vec<SignatureVale>
}

